package org.changs.monitor.camera;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.WindowManager;

import org.changs.aplug.utils.FileUtils;
import org.changs.media.MediaManager;
import org.changs.monitor.view.AutoFitTextureView;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Singleton;

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;

/**
 * Created by yincs on 2017/10/19.
 */

@Singleton
public class CameraTaskManager {

    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();

    ///为了使照片竖直显示
    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    public static final int STATE_DEFAULT = 0;
    public static final int STATE_OPENING = 1;
    public static final int STATE_OPENED = 2;
    public static final int STATE_OPEN_ERROR = 2;

    private String mRecordCameraId = "0";

    @IntDef({STATE_DEFAULT, STATE_OPENING, STATE_OPENED})
    public @interface CAMERA_STATE {
    }


    private Map<String, CameraTask> mCameraTaskMap = new HashMap<>();
    private CameraManager mCameraManager;
    private Context mContext;
    private Handler mHandler = new Handler();


    @Inject
    public CameraTaskManager(Context context) {
        mContext = context;
        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
    }

    public CameraTask getCameraTask(String id) {
        CameraTask cameraTask = mCameraTaskMap.get(id);
        if (cameraTask == null) {
            cameraTask = new CameraTask(id);
            mCameraTaskMap.put(id, cameraTask);
        }
        return cameraTask;
    }

    public void nextFile() {
        Set<CameraTask> set = new HashSet<>(mCameraTaskMap.values());
        for (CameraTask cameraTask : set) {
            cameraTask.getMediaRecorderSurface().nextPath();
        }
    }

    public void takePicture(String cameraId) {
        CameraTask cameraTask = mCameraTaskMap.get(cameraId);
        if (cameraTask != null) {
            cameraTask.getImageReaderSurface().takePicture();
        }
    }

    public void closeAll() {
        Set<CameraTask> set = new HashSet<>(mCameraTaskMap.values());
        for (CameraTask cameraTask : set) {
            cameraTask.closeCamera();
        }
    }

    public static abstract class SurfaceFactory {


        public abstract void updateSession(@NonNull List<Surface> surfaces);

        protected abstract void onConfigured(CaptureRequest.Builder builder);

        public abstract void close();

        protected abstract void updateSessionBeforeCloseSession();
    }


    public class CameraTask {
        private final String mCameraId;
        @CAMERA_STATE
        private int mCameraState = STATE_DEFAULT;
        private CameraCharacteristics mCharacteristics;
        private Size mVideoSize;
        private CameraDevice mCameraDevice;
        private CameraCaptureSession mCameraSession;
        private List<SurfaceFactory> mSurfaceFactory = new ArrayList<>();
        private MediaRecorderSurfaceFactory mMediaRecorderSurface = new MediaRecorderSurfaceFactory();
        private ImageReaderSurfaceFactory mImageReaderSurface = new ImageReaderSurfaceFactory();
        private CameraCaptureSession.StateCallback mCameraSessionCallback;
        private String mFilePath;
        private HandlerThread mBackgroundThread;
        private Handler mBackgroundHandler;
        private int mErrorCount;


        public CameraTask(String cameraId) {
            this.mCameraId = cameraId;
            this.mSurfaceFactory.add(mMediaRecorderSurface);
            this.mSurfaceFactory.add(mImageReaderSurface);


            mBackgroundThread = new HandlerThread("CameraBackground " + cameraId);
            mBackgroundThread.start();
            mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
        }

        @SuppressLint("MissingPermission")
        public void openCamera() {
            if (mCameraState != STATE_DEFAULT) {
                Timber.d("openCamera() 不是在默认状态下，cameraState = " + mCameraState);
                return;
            }
            try {
                mCameraState = STATE_OPENING;
                String[] cameraIdList = mCameraManager.getCameraIdList();
                Timber.d("cameraIdList = " + Arrays.asList(cameraIdList));
                String cameraId = null;
                for (String id : cameraIdList) {
                    if (mCameraId.equals(id)) {
                        cameraId = mCameraId;
                        break;
                    }
                }
                Timber.d("cameraId = " + cameraId);
                if (cameraId == null) {
                    Timber.d("openCamera: CameraId 不存在 ");
                    return;
                }
                mCharacteristics = mCameraManager.getCameraCharacteristics(cameraId);
                StreamConfigurationMap map = mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                mVideoSize = CameraUtils.chooseVideoSize(map.getOutputSizes(MediaRecorder.class));

                mCameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {

                    @Override
                    public void onOpened(@NonNull CameraDevice cameraDevice) {
                        Timber.d("onOpened() called with: cameraDevice = [" + cameraDevice + "]");
                        Timber.d("Thread.currentThread().getName(): " + Thread.currentThread().getName());
                        mErrorCount = 0;
                        mCameraState = STATE_OPENED;
                        mCameraDevice = cameraDevice;
                        mHandler.post(() -> updateSession());
                    }

                    @Override
                    public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                        Timber.d("onDisconnected() called with: cameraDevice = [" + cameraDevice + "]");
                        mCameraDevice = cameraDevice;
                        closeCamera();
//                        openCamera();
                    }

                    @Override
                    public void onError(@NonNull CameraDevice cameraDevice, int error) {
                        Timber.d("onError() called with: cameraDevice = [" + cameraDevice + "], error = [" + error + "]" + " mErrorCount = " + mErrorCount);
                        mCameraDevice = cameraDevice;
//                        handleCameraError();
                        closeCamera();
                    }
                }, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        private void handleCameraError() {
            mErrorCount++;
            closeCamera();
            if (mErrorCount <= 2) {
                mCameraState = STATE_DEFAULT;
                openCamera();
            }
            mCameraState = STATE_OPEN_ERROR;
        }

        int updateSessionCount;

        public void updateSession() {
            if (mCameraState != STATE_OPENED || mCameraDevice == null) {
                Timber.d("start() called with cameraState = " + mCameraState);
                openCamera();
                return;
            }
            if (mCameraSession != null) {
                for (SurfaceFactory surfaceFactory : mSurfaceFactory) {
                    surfaceFactory.updateSessionBeforeCloseSession();
                }
                closeSession();
            }
            List<Surface> surfaces = new ArrayList<>();
            for (SurfaceFactory surfaceFactory : mSurfaceFactory) {
                surfaceFactory.updateSession(surfaces);
            }
            if (surfaces.size() == 0) {
                Timber.d("updateSession() but surfaces.size() == 0");
                return;
            }
            Timber.d("cameraId = " + mCameraId + " ,updateSession() called updateSessionCount = " + updateSessionCount++);
            mCameraSessionCallback = new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    Timber.d("onConfigured() called with: " + "session = [" + session + "]");
                    if (this != mCameraSessionCallback) {
                        Timber.e("onConfigured: session已过期");
                        return;
                    }
                    mCameraSession = session;
                    mHandler.post(() -> configured());
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                    Timber.d("onConfigureFailed() called with: " + "session = [" + session + "]");
                    if (this != mCameraSessionCallback) {
                        Timber.e("onConfigured: session已过期");
                        return;
                    }
                    mCameraSession = session;
                }
            };
            try {
                mCameraDevice.createCaptureSession(surfaces, mCameraSessionCallback, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
                handleCameraError();
            }
        }

        private void configured() {
            if (mCameraDevice == null || mCameraSession == null) return;
            try {
                CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
                for (SurfaceFactory factory : mSurfaceFactory) {
                    factory.onConfigured(builder);
                }
                mCameraSession.setRepeatingRequest(builder.build(), null, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        public void closeSession() {
            mCameraSessionCallback = null;
            if (mCameraSession != null) {
                try {
                    mCameraSession.close();
                } catch (Throwable e) {
                    e.printStackTrace();
                }
                ;
                mCameraSession = null;
            }
        }

        public void closeCamera() {
            Timber.d("closeCamera() called ");
            for (SurfaceFactory surfaceFactory : mSurfaceFactory) {
                surfaceFactory.close();
            }
            try {
                closeSession();
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (mCameraDevice != null) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
            mCameraState = STATE_DEFAULT;
        }

        public MediaRecorderSurfaceFactory getMediaRecorderSurface() {
            return mMediaRecorderSurface;
        }


        public ImageReaderSurfaceFactory getImageReaderSurface() {
            return mImageReaderSurface;
        }

        public TextureSurfaceFactory newTextureSurface(AutoFitTextureView textureView) {
            return new TextureSurfaceFactory(textureView);
        }

        private class MediaRecorderSurfaceFactory extends SurfaceFactory {
            private MediaRecorder mMediaRecorder;
            private boolean isMediaRecording;


            @Override
            protected void updateSessionBeforeCloseSession() {
                close();
            }

            @Override
            public void updateSession(@NonNull List<Surface> surfaces) {
                if (mMediaRecorder == null) {
                    mMediaRecorder = new MediaRecorder();
                    setUpMediaRecorder();
                }
                try {
                    Surface surface = mMediaRecorder.getSurface();
                    if (surface != null) surfaces.add(surface);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            protected void onConfigured(CaptureRequest.Builder builder) {
                builder.addTarget(mMediaRecorder.getSurface());
                builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
                if (!isMediaRecording) {
                    isMediaRecording = true;
                    try {
                        mMediaRecorder.start();
                        Timber.d("cameraId = %s, MediaRecorder.start() success", mCameraId);
                    } catch (IllegalStateException e) {
                        e.printStackTrace();
                        Timber.d("cameraId = %s, MediaRecorder.start() fail", mCameraId);
                    }
                }
            }

            @Override
            public void close() {
                renameFile();
                if (mMediaRecorder != null) {
                    isMediaRecording = false;
                    try {
                        mMediaRecorder.stop();
                    } catch (Exception e) {
//                        e.printStackTrace();
                    }
                    try {
                        mMediaRecorder.reset();
                        mMediaRecorder.release();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    mMediaRecorder = null;
                }
            }


            private void setUpMediaRecorder() {
                boolean recordAudio = mCameraId.equals(mRecordCameraId);
                mFilePath = MediaManager.createVehicleMediaFile(mContext, mCameraId).getAbsolutePath();
                Timber.d("setUpMediaRecorder mFilePath = " + mFilePath);
                if (recordAudio) mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
                mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
                String outputFile = mFilePath + ".temp";
                FileUtils.createFileByDeleteOldFile(outputFile);
                mMediaRecorder.setOutputFile(outputFile);
                mMediaRecorder.setVideoEncodingBitRate(10000000);
                mMediaRecorder.setVideoFrameRate(20);
                mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
                mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
                if (recordAudio) mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
                try {
                    mMediaRecorder.prepare();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }


            public void nextPath() {
                Timber.d("cameraId = " + mCameraId + " ,nextPath: 正在切换文件，isMediaRecording =" + isMediaRecording);
                if (mMediaRecorder == null) return;
                if (isMediaRecording) {

                    mBackgroundHandler.post(() -> {
                        closeSession();
                        close();
                        CameraTask.this.updateSession();
                    });
                }
            }

            private boolean renameFile() {
                if (mFilePath == null) return false;
                String path = mFilePath;
                mFilePath = null;
                File file = new File(path + ".temp");
                Timber.d(file.getName() + ": file.length() = " + file.length());
                if (file.length() < 1024 * 10) {
                    FileUtils.deleteFile(file);
                    return false;
                }
                boolean renameFile = file.renameTo(new File(path));
                Timber.d("renameFile path = " + path + ", result = " + renameFile);
                return renameFile;
            }
        }


        public class TextureSurfaceFactory extends SurfaceFactory {

            private final AutoFitTextureView textureView;
            private Size previewSize;


            public TextureSurfaceFactory(AutoFitTextureView textureView) {
                this.textureView = textureView;
            }

            public void onResume() {
                if (this.textureView.isAvailable()) {
                    mSurfaceFactory.add(TextureSurfaceFactory.this);
                    CameraTask.this.updateSession();
                } else {
                    this.textureView.setSurfaceTextureListener(mSurfaceTextureListener);
                }
            }

            public void onPause() {
//                mSurfaceFactory.remove(TextureSurfaceFactory.this);
//                CameraTask.this.updateSession();
            }

            @Override
            public void updateSession(@NonNull List<Surface> surfaces) {
                if (previewSize == null) {
                    StreamConfigurationMap map = mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                    previewSize = CameraUtils.chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
                            textureView.getWidth(), textureView.getHeight(), mVideoSize);
                    int orientation = mContext.getResources().getConfiguration().orientation;
                    if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                        textureView.setAspectRatio(previewSize.getWidth(), previewSize.getHeight());
                    } else {
                        textureView.setAspectRatio(previewSize.getHeight(), previewSize.getWidth());
                    }
                    textureView.getSurfaceTexture().setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
                    CameraUtils.configureTransform(textureView, previewSize);
                }
                surfaces.add(new Surface(textureView.getSurfaceTexture()));
            }

            @Override
            protected void onConfigured(CaptureRequest.Builder builder) {
                builder.addTarget(new Surface(textureView.getSurfaceTexture()));
            }

            @Override
            public void close() {
            }

            @Override
            protected void updateSessionBeforeCloseSession() {
            }

            private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
                @Override
                public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                    Timber.d("onSurfaceTextureAvailable() called with: surface = [" + surface + "], width = [" + width + "], height = [" + height + "]");
                    mSurfaceFactory.add(TextureSurfaceFactory.this);
                    CameraTask.this.updateSession();
                }

                @Override
                public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
                    CameraUtils.configureTransform(textureView, previewSize);
                }

                @Override
                public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                    Timber.d("onSurfaceTextureDestroyed() called with: surface = [" + surface + "]");
                    mSurfaceFactory.remove(TextureSurfaceFactory.this);
                    CameraTask.this.updateSession();
                    return false;
                }

                @Override
                public void onSurfaceTextureUpdated(SurfaceTexture surface) {

                }
            };

        }


        private class ImageReaderSurfaceFactory extends SurfaceFactory {

            private ImageReader mImageReader;

            @Override
            protected void updateSessionBeforeCloseSession() {
                close();
            }

            @Override
            public void updateSession(@NonNull List<Surface> surfaces) {
                if (mImageReader == null) {
                    StreamConfigurationMap map = mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                    Size imageSize = CameraUtils.chooseVideoSize(map.getOutputSizes(ImageFormat.JPEG));
                    mImageReader = ImageReader.newInstance(imageSize.getWidth(), imageSize.getHeight(), ImageFormat.JPEG, 1);
                    mImageReader.setOnImageAvailableListener(reader -> {
                        Observable
                                .just(new ImageSaver(reader.acquireNextImage(), MediaManager.createVehiclePictureFile(mContext, mCameraId)))
                                .doOnNext(ImageSaver::run)
                                .observeOn(Schedulers.io())
                                .subscribe();
                    }, mBackgroundHandler);
                }
                surfaces.add(mImageReader.getSurface());
            }

            @Override
            protected void onConfigured(CaptureRequest.Builder builder) {
            }

            @Override
            public void close() {
                if (mImageReader != null) {
                    mImageReader.close();
                    mImageReader = null;
                }
            }


            public void takePicture() {
                if (mImageReader == null || mCameraDevice == null || mCameraSession == null) return;
                // 创建拍照需要的CaptureRequest.Builder
                final CaptureRequest.Builder captureRequestBuilder;
                try {
                    captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                    // 将imageReader的surface作为CaptureRequest.Builder的目标
                    captureRequestBuilder.addTarget(mImageReader.getSurface());
                    // 自动对焦
                    captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                    // 自动曝光
                    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                    // 获取手机方向
                    WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
                    int rotation = windowManager.getDefaultDisplay().getRotation();
                    // 根据设备方向计算设置照片的方向
                    captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
                    //拍照
                    CaptureRequest mCaptureRequest = captureRequestBuilder.build();
                    mCameraSession.capture(mCaptureRequest, new CameraCaptureSession.CaptureCallback() {
                        @Override
                        public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                            super.onCaptureCompleted(session, request, result);
                            Timber.d("onCaptureCompleted() called with: session = [" + session + "], request = [" + request + "], result = [" + result + "]");
                        }

                        @Override
                        public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
                            super.onCaptureFailed(session, request, failure);
                            Timber.d("onCaptureFailed() called with: session = [" + session + "], request = [" + request + "], failure = [" + failure.getReason() + "]");
                        }
                    }, mBackgroundHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static class ImageSaver implements Runnable {

        private final Image mImage;
        private final File mFile;

        ImageSaver(Image image, File file) {
            mImage = image;
            mFile = file;
        }

        @Override
        public void run() {
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            FileOutputStream output = null;
            try {
                FileUtils.createFileByDeleteOldFile(mFile);
                output = new FileOutputStream(mFile);
                output.write(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mImage.close();
                if (null != output) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }


}
